package aino;

import java.io.*;
import java.util.*;
import java.lang.Object;
import java.io.FilenameFilter;

/**
 * Title: Aino main fle
 * Description: Processor independent stuff (in theory!)
 * Copyright:    Copyright (c) 2004 Hannu Jokinen
 */

public class Aino {
    static Vector fileList;
    static Vector functions;
    static Vector classes;
    static Vector variables;
    static Vector initlines;
    static Vector strings;
    static Vector interrupts;
    static Method[] meths;
    static boolean[][] fMatrix;
    static boolean[][] callMatrix;
    static int mSize = 0;
    static int memStart = 10000;
    static int memEnd = 0;
    static int clockSpeed = 4000000;
    static int heap = 40;
    static int bankSize = 0x80;
    static String processorType;
    static String mainName;
    static PrintWriter output;
    static int lastPushed;
    static boolean[] freeMemory;
    static String crlf = "";
    static boolean verbose = false;
    static int labels = 0;
    static boolean mul = false;
    static boolean div = false;
    static boolean mod = false;

    private static final Filter _filter = new Filter();

    public Aino() {
	functions = new Vector();
	classes = new Vector();
	variables = new Vector();
	initlines = new Vector();
	strings = new Vector();
	interrupts = new Vector();
	mainName = "unknown";
    }

    public static void main(String[] args) {
	Aino aino1 = new Aino();
	fileList = new java.util.Vector();
	java.util.Vector classList = new java.util.Vector();
	freeMemory = new boolean[1024];
	for (int i = 0; i < 1024; i++) freeMemory[i] = false;

	for (int i = 0; i < args.length; i++) {
	    if (args[i].charAt(0) != '-') {
		addClassFile(args[i], fileList);
	    } else {
		if (args[i].equals("-crlf")) {
		    crlf = "\r";
		} else if(args[i].equals("-verbose")) {
		    verbose = true;
		} else if(args[i].equals("-debug")) {
		    Log.log_level = 1;
		}
	    }
	} // for

	System.out.print("Creating:    ");
	for (int i = 0; i < fileList.size(); i++) {
	    String fileName = (String)fileList.elementAt(i);
	    System.out.print(" "+fileName+".class");
	    createFiles(fileName);
	}
	System.out.print("\nReading:     ");

	File f = new File(".");
	String[] files = f.list();

	for (int i = 0; i < files.length; i++) {
	    if (files[i].endsWith(".class")) {
		Class c = new Class(files[i]);
		classes.add(c);
		System.out.print(" "+files[i]);
	    }
	}

	System.out.print("\nMemory alloc:");

	for (int i = 0; i < classes.size(); i++) {
	    Class c = (Class)classes.elementAt(i);
	    System.out.print(" "+c.name);
	    c.calcStuff();
	}

	try {
	    if (mainName.equals("unknown")) {
		Class c = (Class)classes.elementAt(0);
		mainName = c.name;
	    }
	    output = new PrintWriter(new BufferedWriter
				     (new FileWriter(mainName+".asm")));

	    System.out.print("\nCompiling:   ");
	    createMap();

	    output.println("clockspeed\tequ\t"+clockSpeed+crlf);
	    // the best values for 1 ms clock
	    double clock = clockSpeed/4;
	    double bestprescaler = 0;
	    double besttmr0 = 0;
	    double bestdiff = 100.0;
	    for (double prescaler = 2.0; prescaler < 257.0; prescaler *= 2.0) {
		double tmr0 = Math.floor(0.001*clock/prescaler);
		if (tmr0 > 256.0) continue;

		double diff1 = Math.abs(tmr0*prescaler/clock - 0.001);
		double diff2 = Math.abs((tmr0+1.0)*prescaler/clock - 0.001);

		if (diff1 < bestdiff) {
		    bestprescaler = prescaler;
		    besttmr0 = tmr0;
		    bestdiff = diff1;
		}
		if (diff2 < bestdiff && tmr0 < 256.0) {
		    bestprescaler = prescaler;
		    besttmr0 = tmr0 + 1.0;
		    bestdiff = diff2;
		}
	    }
	    if (bestdiff > 1.0) {
		System.out.println("\n*** Warning: Cannot calculate prescaler "+
				   "and tmr0 values for 1 ms interrupt (clock = "+
				   clockSpeed+")");
	    } else {
		output.println("; tmr0 count = "+((int)besttmr0)+crlf);
		output.println("tmr0_for_1ms\tequ\t"+(256-(int)besttmr0)+crlf);
		output.println("; prescaler = "+(int)bestprescaler+crlf);
		output.println("set_prescaler_for_1ms\tmacro"+crlf);
		switch ((int)bestprescaler) {
		case 32: case 64: case 128: case 256:
		    output.println("\tbsf\t_OPTION,2"+crlf);
		    break;
		default:
		    output.println("\tbcf\t_OPTION,2"+crlf);
		}
		switch ((int)bestprescaler) {
		case 8: case 16: case 128: case 256:
		    output.println("\tbsf\t_OPTION,1"+crlf);
		    break;
		default:
		    output.println("\tbcf\t_OPTION,1"+crlf);
		}
		switch ((int)bestprescaler) {
		case 4: case 16: case 64: case 256:
		    output.println("\tbsf\t_OPTION,0"+crlf);
		    break;
		default:
		    output.println("\tbcf\t_OPTION,0"+crlf);
		}
		output.println("\tendm"+crlf);
	    }

	    for (int i = 0; i < classes.size(); i++) {
		Class c = (Class)classes.elementAt(i);
		System.out.print(" "+c.name);
		for (int j = 0; j < c.methodsCount; j++) {
		    Method m = c.methods[j];
		    if (c.getConst(m.nameIndex).charAt(0) != '_') {
			Pic p = new Pic(output);
			p.compile(m);
		    }
		}
	    }
	    
	    // Processor dependent stuff. Should be moved elsewhere.
	    output.println("interrupt"+crlf);
	    output.println("\tmovwf\t_W_TEMP"+crlf);
	    output.println("\tswapf\t_STATUS,0"+crlf);
	    output.println("\tmovwf\t_STATUS_TEMP"+crlf);
	    output.println("\tmovf\t_ACC,0"+crlf);
	    output.println("\tmovwf\t_ACC_TEMP"+crlf);
	    output.println("\tmovf\t4,0"+crlf);
	    output.println("\tmovwf\t_FSR_TEMP"+crlf);
	    output.println("\tbanksel\t_ACC"+crlf);

	    for (int i = 0; i < interrupts.size(); i++) {
		String intf = (String)interrupts.elementAt(i);
		output.println("\tcall\t"+intf+crlf);
	    }

	    output.println(crlf+"\n\tmovf\t_FSR_TEMP,0"+crlf);
	    output.println("\tmovwf\t4"+crlf);
	    output.println("\tmovf\t_ACC_TEMP,0"+crlf);
	    output.println("\tmovwf\t_ACC"+crlf);
	    output.println("\tswapf\t_STATUS_TEMP,0"+crlf);
	    output.println("\tmovwf\t_STATUS"+crlf);

	    output.println("\tswapf\t_W_TEMP,1"+crlf);
	    output.println("\tswapf\t_W_TEMP,0"+crlf);
	    output.println("\tretfie"+crlf);

	    
	    output.println("\tend"+crlf);
	    output.close();
	    System.out.println();
	} catch (Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }

    static void addClassFile(String name, java.util.Vector list) {
	// normalize the file name
	String fileName = parseFileDir(name) + parseFileName(name);
	// + "." + parseFileExt(name);

	// don't add the primitive types since there is no class file associated with them
	if (fileName.equals(".class")) return;
	if (fileName.equals("byte.class")) return;
	if (fileName.equals("short.class")) return;
	if (fileName.equals("int.class")) return;
	if (fileName.equals("long.class")) return;
	if (fileName.equals("boolean.class")) return;
	if (fileName.equals("float.class")) return;
	if (fileName.equals("double.class")) return;
	if (fileName.equals("char.class")) return;
	if (fileName.equals("void.class")) return;

	// check if file has already been read in
	for (int i = 0; i < list.size(); i++) {
	    if (((String)list.elementAt(i)).equals(fileName)) return;
	}

	// add class to the list of those read
	list.add(fileName);
    }

    public static String parseFileDir(String s) {
	String fileDir = s;
	fileDir = fileDir.replace('/', File.separatorChar);
	fileDir = fileDir.replace('\\', File.separatorChar);
	int i = fileDir.lastIndexOf('.');
	if (i > 0) {
	    fileDir = fileDir.substring(0, i).replace('.', File.separatorChar) + fileDir.substring(i);
	}
	if (fileDir.lastIndexOf(File.separatorChar) >= 0) {
	    fileDir = fileDir.substring(0, fileDir.lastIndexOf(File.separatorChar) + 1);
	} else {
	    fileDir = "";
	}
	fileDir = fileDir.replace('.', File.separatorChar);
	if (!fileDir.equals("")) {
	    if (fileDir.charAt(fileDir.length()-1) != File.separatorChar) fileDir += File.separatorChar;
	}
	return fileDir;
    }

    public static String parseFileName(String s) {
	String fileName = s;
	fileName = fileName.replace('/', File.separatorChar);
	fileName = fileName.replace('\\', File.separatorChar);
	int i = fileName.lastIndexOf('.');
	if (i > 0) {
	    fileName = fileName.substring(0, i).replace('.', File.separatorChar) + fileName.substring(i);
	}
	if (fileName.lastIndexOf(File.separatorChar) >= 0) {
	    fileName = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1);
	}
	if (fileName.lastIndexOf(".") >= 0) {
	    fileName = fileName.substring(0, fileName.lastIndexOf("."));
	}
	return fileName;
    }

    public static String parseFileExt(String s) {
	String fileExt = s;
	if (fileExt.lastIndexOf(".") >= 0) {
	    fileExt = fileExt.substring(fileExt.lastIndexOf(".") + 1);
	} else {
	    fileExt = "class";
	}
	return fileExt;
    }

    private static void createFiles(String fileName) {
	String directory = parseFileDir(fileName);
	String name = parseFileName(fileName);
	File classFile = new File(directory+name+".class");
	File javaFile = new File(directory+name+".java");
	File byteFile = new File(directory+name+".byte");
	
	if (directory.equals("")) directory = ".";

	if (!javaFile.exists()) {
	    System.out.println("\n*** Error: No java file found!");
	    System.exit(1);
	}

	if ((!classFile.exists()) || classFile.lastModified()<javaFile.lastModified()) {
	    try {
		String line;
		String[] cmd = {"javac", name+".java"};

		Process p = Runtime.getRuntime().exec(cmd);
		BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));

		String e;
		while ((e = err.readLine()) != null)
		    System.out.println(e);
		err.close();
	    } catch (Exception err) {
		err.printStackTrace();
		System.exit(1);
	    }
	} else {
	    System.out.print(" (up-to-date)");
	}	    
    }

    public static Function getFunction(String name) {
	for (int i = 0; i < functions.size(); i++) {
	    Function f = (Function)functions.elementAt(i);
	    if (f.name.equals(name)) return f;
	}
	return null;
    }

    public static Function getFunctionByDefinition(String defline) {
	int a = defline.indexOf('(');
	if (a < 0) return null;
	int b = defline.lastIndexOf(' ', a);
	int c = defline.indexOf(')', a);
	String orig = defline.substring(b+1, c+1);

	for (int i = 0; i < functions.size(); i++) {
	    Function f = (Function)functions.elementAt(i);
	    if (f.definition.equals(orig)) return f;
	}
	return null;
    }

    public static int getFunctionIndex(String name) {
	for (int i = 0; i < functions.size(); i++) {
	    Function f = (Function)functions.elementAt(i);
	    if (f.name.equals(name)) return i;
	}
	return -1;
    }

    public static Class getClass(String name) {
	int a = name.indexOf(';');
	if (a > 0) {
	    int b = name.indexOf('L');
	    name = name.substring(b+1, a);
	}
	for (int i = 0; i < classes.size(); i++) {
	    Class c = (Class)classes.elementAt(i);
	    if (c.name.equals(name)) return c;
	}
	return null;
    }

    public static void createMap() {
	memStart += 6; // space for _ACC & _HEAP & interrrupt stuff
	int origMemStart = memStart;
	mSize = 0;

	// find class statics size & number of methods
	for (int i = 0; i < classes.size(); i++) {
	    Class c = (Class)classes.elementAt(i);
	    c.startAddr = memStart;
	    memStart += c.staticLen;
	    mSize += c.methodsCount;
	}

	// memStart points to the first free memory location
	// fill in the meths table with all the methods
	int m = 0;
	fMatrix = new boolean[mSize][mSize];
	callMatrix = new boolean[mSize][mSize];
	meths = new Method[mSize];
	for (int i = 0; i < classes.size(); i++) {
	    Class c = (Class)classes.elementAt(i);
	    for (int j = 0; j < c.methodsCount; j++) {
		meths[m++] = c.methods[j];
	    }
	}

	// create the call matrix
	for (int i = 0; i < mSize; i++) {
	    Method method = meths[i];
	    for (int j = 0; j < method.calls.size(); j++) {
		Method called = (Method)method.calls.elementAt(j);
		int pos;
		for (pos = 0; pos < mSize; pos++) {
		    if (meths[pos] == called) break;
		}
		if (pos >= mSize) {
		    System.out.println("\n*** Warning: Unknown func called.");
		    continue;
		}
		fMatrix[i][pos] = true;
		callMatrix[i][pos] = true;
	    }
	}

	// calculate the final matrix
	for (int i = 0; i < mSize; i++) {
	    for (int j = 0; j < mSize; j++) {
		if (i == j) continue;
		if (fMatrix[i][j]) {
		    for (int k = 0; k < mSize; k++) {
			if (fMatrix[j][k]) fMatrix[i][k] = true;
		    }
		}
	    }
	}
	for (int i = 0; i < mSize; i++) {
	    for (int j = 0; j < mSize; j++) {
		if (fMatrix[i][j]) fMatrix[j][i] = true;
	    }
	}

	// check for legal addresses
	for (int i = 0; i < mSize; i++) {
	    Method method = meths[i];
	    int wanted = method.getMaxLocals() + method.getMaxStack();
	    for (int j = memStart; j < memEnd-wanted; j++) {
		if (legalAddress(i, j, wanted)) {
		    method.firstAddr = j;
		    method.lastAddr = j+wanted;
		    break;
		}
	    }
	}
		
	try {
	    for (int i = 0; i < initlines.size(); i++) {
		String line = (String)initlines.elementAt(i);
		output.println(line+crlf);
	    }

	    int addr = origMemStart;
	    // calc class statics 
	    for (int i = 0; i < classes.size(); i++) {
		Class c = (Class)classes.elementAt(i);
		output.println(fixStr(c.name)+"_static\tequ\t"+addr+crlf);
		variables.add(new VarAddr(fixStr(c.name)+"_static", addr));
		addr += c.staticLen;
	    }

	    for (int i = 0; i < classes.size(); i++) {
		Class c = (Class)classes.elementAt(i);
		int superSize = getSuperClassInstanceLength(c);
		for (int j = 0; j < c.fieldsCount; j++) {
		    Field f = c.fields[j];
		    if ((f.accessFlags & 0x0008) == 0) {
			output.println(fixStr(c.name)+"_"+
				       fixStr(c.getConst(f.nameIndex))+
				       "\tequ\t"+(f.address+superSize)+crlf);
		    } else {
			// static variable
			String var = fixStr(c.name)+"_"+fixStr(c.getConst(f.nameIndex));
			output.println(var+"\tequ\t"+(f.address+c.startAddr)+crlf);
			variables.add(new VarAddr(var, f.address+c.startAddr));
		    }
		}
	    }

	    for (int i = 0; i < mSize; i++) {
		Method mt = meths[i];
		output.println(mt.getMethodName()+
			       "_loc\tequ\t"+mt.firstAddr+crlf);
		output.println(mt.getMethodName()+
			       "_tmp\tequ\t"+(mt.firstAddr+mt.getMaxLocals())+
			       crlf);
		variables.add(new VarAddr(mt.getMethodName()+"_loc", mt.firstAddr));
		variables.add(new VarAddr(mt.getMethodName()+"_tmp", 
					  mt.firstAddr+mt.getMaxLocals()));
	    }

	    output.println("_FSR_TEMP\tequ\t"+(origMemStart-6)+crlf);
	    output.println("_ACC\tequ\t"+(origMemStart-5)+crlf);
	    output.println("_HEAP\tequ\t"+(origMemStart-4)+crlf);
	    output.println("_ACC_TEMP\tequ\t"+(origMemStart-3)+crlf);
	    output.println("_STATUS_TEMP\tequ\t"+(origMemStart-2)+crlf);
	    output.println("_W_TEMP\tequ\t"+(origMemStart-1)+crlf);

	    variables.add(new VarAddr("_STATUS", 3));
	    variables.add(new VarAddr("_FSR_TEMP", origMemStart-6));
	    variables.add(new VarAddr("_ACC", origMemStart-5));
	    variables.add(new VarAddr("_HEAP", origMemStart-4));
	    variables.add(new VarAddr("_ACC_TEMP", origMemStart-3));
	    variables.add(new VarAddr("_STATUS_TEMP", origMemStart-2));
	    variables.add(new VarAddr("_W_TEMP", origMemStart-1));

	    output.println("_start"+crlf);

	    output.println("\tmovlw\t"+heap+crlf);
	    output.println("\tmovwf\t_HEAP"+crlf);

	    for (int i = 0; i < mSize; i++) {
		Method method = meths[i];
		if (method.cls.getConst(method.nameIndex).equals("<clinit>")) {
		    output.println("\tcall\t"+method.getMethodName()+crlf);
		}
	    }

	    output.println("\tgoto\tmain"+crlf);

	} catch (Exception e) {
	}
    }

    private static boolean legalAddress(int func, int addr) {
	if (!freeMemory[addr]) return false;
	for (int i = 0; i < mSize; i++) {
	    try {
		if (fMatrix[func][i]) {
		    Method called = meths[i];
		    if (called.firstAddr <= addr && called.lastAddr > addr) {
			return false;
		    }
		}
	    } catch (Exception e) {
		System.out.println("\n*** Error: Legal address check failure");
	    }
	}
	return true;
    }

    private static boolean legalAddress(int func, int start, int size) {
	for (int i = start; i < start+size; i++)
	    if (legalAddress(func, i) == false) return false;
	return true;
    }

    public static Method findMethod(String className, 
				    String funcName, 
				    String argsName) {
	for (int i = 0; i < classes.size(); i++) {
	    Class c = (Class)classes.elementAt(i);
	    if (c.name.equals(className)) {
		for (int j = 0; j < c.methodsCount; j++) {
		    Method m = c.methods[j];
		    if (c.getConst(m.nameIndex).equals(funcName) &&
			c.getConst(m.descriptorIndex).equals(argsName))
			return m;
		}
	    }
	}
	return null;
    }

    public static String fixStr(String s) {
	if (s.indexOf("String") >= 0) return "_Str";

	if (s.indexOf('<') >= 0 || s.indexOf('>') >= 0) {
	    s = s.replace('<', '_');
	    s = s.replace('>', '_');
	}
	if (s.indexOf('(') >= 0 || s.indexOf(')') >= 0) {
	    s = s.replace('(', '_');
	    s = s.replace(')', '_');
	}
	if (s.indexOf(';') >= 0) {
	    s = s.replace(';', '_');
	}
	if (s.indexOf('[') >= 0) {
	    s = s.replace('[', 'A');
	}
	if (s.indexOf('/') >= 0) {
	    s = s.replace('/', 'x');
	}
	if (s.indexOf('$') >= 0) {
	    s = s.replace('$', 'z');
	}
	return s;
    }

    public static int getSuperClassInstanceLength(Class c) {
	String sc = c.getSuperClass();
	if (sc == null) return 0;
	Class superClass = getClass(sc);
	int len = 0;
	while (superClass != null) {
	    len += superClass.instanceLen;
	    sc = superClass.getSuperClass();
	    if (sc == null) return len;
	    superClass = getClass(sc);
	}
	return len;
    }

    public static int getVarAddress(String var) {
	if (var.length() == 0) return 0;
	if (var.charAt(0) >= '0' && var.charAt(0) <= '9') {
	    int result = Integer.decode(var).intValue();
	    if (result >= 2 && result <= 4) return -1;
	    else return result;
	}

	String vName;
	int x = var.indexOf('+');
	if (x > 0) vName = var.substring(0, x);
	else vName = var;

	for (int i = 0; i < variables.size(); i++) {
	    VarAddr v = (VarAddr)variables.elementAt(i);
	    if (v.name.equals(vName)) {
		if (v.addr >= 2 && v.addr <= 4) return -1;
		else return v.addr;
	    }
	}
	// System.out.println("could not find var "+var);
	return -1;
    }

    public static int getStringNumber(String var) {
	for (int i = 0; i < strings.size(); i++) {
	    String s = (String) strings.elementAt(i);
	    if (s.equals(var)) {
		return i;
	    }
	}
	return -1;
    }

    private static final class Filter implements FilenameFilter {
	private Filter() {
	}

	public boolean accept(File aDirectory, String aName) {
	    if (aName.endsWith(".class")) {
		File aFile = new File( aDirectory, aName );
		if ( aFile.isDirectory() == true ) {
		    return true;
		}
	    }
	    return false;
	}
    }
}
